﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Windows.Forms;

namespace SynoManager.DownloadStation.Ftp
{
    public partial class FtpBrowserForm : Form
    {
        private readonly DownloadStationService service;

        public FtpBrowserForm()
        {
            InitializeComponent();

            this.Icon = Icon.FromHandle(Properties.Resources.server_compressed.GetHicon());

            try
            {
                View view = (View)Enum.Parse(typeof (View), Properties.Settings.Default.FtpBrowserView);
                SetView(view);
            }
            catch (Exception)
            {
                listView.View = View.List;
            }
        }

        public FtpBrowserForm(DownloadStationService service)
            :this()
        {
            this.service = service;
        }

        #region Connection Management

        internal FtpClient ftpClient;
        public void Connect(string address, string username, string password, bool usePassive)
        {
            ftpClient = new FtpClient(address, username, password, usePassive);

            this.Text = "SynoManager FTP Browser - " + address;
            hostLabel.Text = address;
            userLabel.Text = username;

            LoadFolderIcons();

            LoadRoot();
        } 
        #endregion

        #region Directory Loading
        private string currentDirectory;
        private FtpDirectoryInfo ftpDirectory;
        private string queuedDirectory;

        private void LoadRoot()
        {
            treeView.Nodes.Clear();
            treeView.Nodes.Add("/", ftpClient.Host);
            LoadDirectory("/");
        }

        private void LoadDirectory(string relativePath)
        {
            if (backgroundWorker1.IsBusy)
            {
                queuedDirectory = relativePath;
                return;
            }

            queuedDirectory = null;
            folderLabel.Text = currentDirectory = relativePath;
            toolStripProgressBar.Visible = true;
            listView.Items.Clear();

            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            ftpDirectory = null;
            try
            {
                ftpDirectory = ftpClient.GetDirectory(currentDirectory);
            }
            catch (ApplicationException ex)
            {
                MessageBox.Show("Could not retrieve directory listing:\n" + ex.Message, "SynoManager FTP Browser",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (WebException ex)
            {
                MessageBox.Show("Could not connect to FTP server:\n" + ex.Message, "SynoManager FTP Browser",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                MessageBox.Show("Could not connect to FTP server socket:\n" + ex.Message, "SynoManager FTP Browser",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (ftpDirectory == null)
            {
                toolStripProgressBar.Visible = false;
                return;
            }

            var nodes = treeView.Nodes.Find(currentDirectory, true);
            Debug.Assert(nodes.Length == 1, "Could not locate directory '" + currentDirectory + "' in the tree.");
            TreeNode node = nodes[0];
            if (node.Nodes.Count == 0)
            {
                foreach (FtpDirectoryEntry dir in ftpDirectory.GetDirectories())
                {
                    if (dir.Name == "." || dir.Name == "..")
                        continue;

                    node.Nodes.Add(dir.Path, dir.Name, closedFolderIconKey, openFolderIconKey).Tag = dir;
                }
                node.Expand();
            }

            if (currentDirectory != "/")
            {
                // add a node to the parent directory
                ListViewItem lvi = listView.Items.Add("..", "..", closedFolderIconKey);
                lvi.SubItems.Add("<Parent Directory>");
                lvi.SubItems.Add("");
                lvi.SubItems.Add("");
                lvi.Tag = "..";
            }

            foreach (FtpDirectoryEntry entry in ftpDirectory.OrderByDescending(x => x.IsDirectory))
            {
                if (entry.Name == "." || entry.Name == "..")
                    continue;

                string imageKey;
                if (entry.IsDirectory)
                {
                    imageKey = closedFolderIconKey;
                }
                else
                {
                    string filename = entry.Name;
                    int index = filename.IndexOf('.');
                    if (index > 0)
                        imageKey = filename.Substring(index);
                    else
                        imageKey = filename;

                    LoadFileIcon(imageKey);
                }

                ListViewItem lvi = listView.Items.Add(entry.Path, entry.Name, imageKey);
                lvi.SubItems.Add(entry.IsDirectory ? "<Folder>" : imageKey);
                lvi.SubItems.Add(entry.Size.ToByteString());
                lvi.SubItems.Add(entry.Permissions);
                lvi.Tag = entry;
            }
            listView_SelectedIndexChanged(null, null);
            toolStripProgressBar.Visible = false;

            if (queuedDirectory != null)
                LoadDirectory(queuedDirectory);
        }
        #endregion

        #region Images / Icons
        private const string openFolderIconKey = "$OPENFOLDER$";
        private const string closedFolderIconKey = "$CLOSEDFOLDER$";
        private Icon defaultIconSmall;
        private Icon defaultIconLarge;

        private void LoadFolderIcons()
        {
            imageListSmall.Images.Add(openFolderIconKey, IconHelper.GetFolderIcon(IconHelper.IconSize.Small, IconHelper.FolderType.Open));
            imageListSmall.Images.Add(closedFolderIconKey, IconHelper.GetFolderIcon(IconHelper.IconSize.Small, IconHelper.FolderType.Closed));
            imageListLarge.Images.Add(openFolderIconKey, IconHelper.GetFolderIcon(IconHelper.IconSize.Large, IconHelper.FolderType.Open));
            imageListLarge.Images.Add(closedFolderIconKey, IconHelper.GetFolderIcon(IconHelper.IconSize.Large, IconHelper.FolderType.Closed));
            defaultIconSmall = IconHelper.GetSysIcon(0, IconHelper.IconSize.Small);
            defaultIconLarge = IconHelper.GetSysIcon(0, IconHelper.IconSize.Large);
        }

        private void LoadFileIcon(string extension)
        {
            if (!imageListSmall.Images.ContainsKey(extension))
            {
                Icon small = IconHelper.GetExtensionIcon(extension, IconHelper.IconSize.Small);
                if (small != null)
                    imageListSmall.Images.Add(extension, small);
                else
                    imageListSmall.Images.Add(extension, defaultIconSmall);
            }
            if (!imageListLarge.Images.ContainsKey(extension))
            {
                Icon large = IconHelper.GetExtensionIcon(extension, IconHelper.IconSize.Large);
                if (large != null)
                    imageListLarge.Images.Add(extension, defaultIconLarge);
            }
        }
        #endregion

        #region ToolStrip Buttons
        private void refreshButton_Click(object sender, EventArgs e)
        {
            LoadDirectory(currentDirectory);
        }

        private void closeButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void tilesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetView(View.Tile);
        }

        private void iconsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetView(View.SmallIcon);
        }

        private void listToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetView(View.List);
        }

        private void detailsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetView(View.Details);
        }

        private void smallIconsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetView(View.SmallIcon);
        }

        private void largeIconsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetView(View.LargeIcon);
        }

        private void SetView(View view)
        {
            listView.View = view;
            Properties.Settings.Default.FtpBrowserView = view.ToString();
            switch(view)
            {
                case View.LargeIcon:
                    viewToolStripSplitButton.Image = Properties.Resources.application_view_icons;
                    break;
                case View.Details:
                    viewToolStripSplitButton.Image = Properties.Resources.application_view_detail;
                    break;
                case View.SmallIcon:
                    viewToolStripSplitButton.Image = Properties.Resources.application_view_icons;
                    break;
                case View.List:
                    viewToolStripSplitButton.Image = Properties.Resources.application_view_list;
                    break;
                case View.Tile:
                    viewToolStripSplitButton.Image = Properties.Resources.application_view_tile;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("view");
            }
        }
        #endregion

        private void listView_DoubleClick(object sender, EventArgs e)
        {
            if (listView.SelectedItems.Count == 1)
            {
                object tag = listView.SelectedItems[0].Tag;
                if (tag is FtpDirectoryEntry)
                {
                    FtpDirectoryEntry entry = (FtpDirectoryEntry)listView.SelectedItems[0].Tag;
                    if (entry.IsDirectory)
                    {
                        LoadDirectory(entry.Path);
                    }
                    else
                    {
                        Download(entry);
                    }
                }
                else if (tag is string)
                {
                    if (String.Equals(tag, ".."))
                    {
                        string path = currentDirectory.Substring(0, currentDirectory.TrimEnd('/').LastIndexOf('/') + 1);
                        LoadDirectory(path);
                    }

                }
            }
        }

        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            FtpDirectoryEntry info = (FtpDirectoryEntry) e.Node.Tag;
            LoadDirectory(info.Path);
        }

        private void listView_SelectedIndexChanged(object sender, EventArgs e)
        {
            bool canDownload = true;
            if (listView.SelectedItems.Count == 0)
            {
                canDownload = false;
            }
            else
            {
                foreach(ListViewItem lvi in listView.SelectedItems)
                {
                    if (!(lvi.Tag is FtpDirectoryEntry))
                    {
                        canDownload = false;
                        continue;
                    }
                }
            }
            downloadToolStripButton.Enabled = canDownload;
        }

        private void downloadToolStripButton_Click(object sender, EventArgs e)
        {
            if (listView.SelectedItems.Count > 5)
            {
                if (DialogResult.No == MessageBox.Show(this, "You're about to download a lot of files.\nAre you sure you want to download the selected files?", "SynoManager FTP Browser", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1))
                    return;
            }
            List<FtpDirectoryEntry> files = new List<FtpDirectoryEntry>();
            foreach (ListViewItem lvi in listView.SelectedItems)
                files.Add((FtpDirectoryEntry) lvi.Tag);

            int directoryCount = files.Count(x => x.IsDirectory);
            if (directoryCount > 0)
            {
                if (DialogResult.No == MessageBox.Show(this, "You are about to download 1 or more directories.\nAre you sure you want to continue?\nAll child folders and files will be downloaded and that may take a while.", "SynoManager FTP Browser", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1))
                    return;
            }

            Download(files.ToArray());
        }

        private void Download(FtpDirectoryEntry[] entries)
        {
            int directoryCount = entries.Count(x => x.IsDirectory);
            if (entries.Length <= 2 && directoryCount == 0)
            {
                foreach(FtpDirectoryEntry entry in entries)
                    Download(entry);
            }
            else
            {
                AddFtpTasksDialog dialog = new AddFtpTasksDialog(this, entries);
                dialog.ShowDialog(this);
            }
        }

        internal void Download(FtpDirectoryEntry entry)
        {
            Debug.Assert(!entry.IsDirectory);
            string url = String.Format("ftp://{0}:{1}@{2}{3}",
                                       ftpClient.Username,
                                       ftpClient.Password,
                                       ftpClient.Host,
                                       entry.Path);
            Debug.WriteLine("Added url: " + url, "FtpBrowser");
            service.AddUrl(url);
        }
    }
}
